package com.frank.oj.core;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.frank.model.entity.judge.JudgeServer;
import com.frank.model.entity.judge.RemoteJudgeAccount;
import com.frank.oj.mapper.RemoteJudgeAccountMapper;
import com.frank.oj.service.entity.judge.JudgeServerEntityService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * 筛选判题机
 *
 * @author frank
 * @Date 2024/4/24
 */
@Component
@Slf4j(topic = "hcoj")
public class ChooseUtils {
    @Resource
    private NacosDiscoveryProperties discoveryProperties;

    @Value("${service-url.name}")
    private String JudgeServiceName;

    @Resource
    private JudgeServerEntityService judgeServerEntityService;

    @Resource
    private RemoteJudgeAccountMapper remoteJudgeAccountMapper;

    public static final boolean openCodeforcesFixServer = false;

    /**
     * 筛选判题机
     *
     * @param isRemote 是否为远程判题
     */
    @Transactional(rollbackFor = Exception.class)
    public JudgeServer chooseServer(Boolean isRemote) {
        List<Instance> instances = getInstances(JudgeServiceName);

        if (instances.isEmpty()) {
            return null;
        }
        List<String> keyList = new ArrayList<>();

        // 筛选出可用的判题机的ip
        instances.forEach(instance -> {
            keyList.add(instance.getIp() + ":" + instance.getPort());
        });
        // 过滤出小于或等于规定最大并发判题任务数的服务实例且健康的判题机
        LambdaQueryWrapper<JudgeServer> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(JudgeServer::getUrl, keyList)
                .eq(JudgeServer::getIsRemote, isRemote)
                .orderByAsc(JudgeServer::getTaskNumber)
                .last("for update"); // 悲观锁：获取到锁后，其他线程无法获取到锁，直到当前线程释放锁

        List<JudgeServer> judgeServerList = judgeServerEntityService.list(queryWrapper);
        for (JudgeServer judgeServer : judgeServerList) {
            if (judgeServer.getTaskNumber() < judgeServer.getMaxTaskNumber()) {
                judgeServer.setTaskNumber(judgeServer.getTaskNumber() + 1);
                boolean updated = judgeServerEntityService.updateById(judgeServer);
                if (updated) {
                    return judgeServer;
                }
            }
        }
        return null;
    }

    /**
     * 筛选固定判题机（一题一机）
     */
    @Transactional(rollbackFor = Exception.class)
    public JudgeServer chooseFixedServer(Boolean isRemote, String fixedTag, Integer index, Integer total) {
        // 获取该微服务的所有健康实例
        List<Instance> instances = getInstances(JudgeServiceName);
        if (instances.isEmpty()) {
            return null;
        }
        List<String> keyList = new ArrayList<>();
        // 获取当前健康实例取出ip和port拼接
        for (Instance instance : instances) {
            keyList.add(instance.getIp() + ":" + instance.getPort());
        }

        // 过滤出小于或等于规定最大并发判题任务数的服务实例且健康的判题机
        QueryWrapper<JudgeServer> judgeServerQueryWrapper = new QueryWrapper<>();
        judgeServerQueryWrapper
                .in("url", keyList)
                .eq("is_remote", isRemote)
                .last("for update"); // 开启悲观锁

        List<JudgeServer> judgeServerList = judgeServerEntityService.list(judgeServerQueryWrapper);
        // CF的VJ判題需要一机一题(根据序号保持一定的固定)
        int len = judgeServerList.size();
        for (int i = 0; i < len; i++) {
            if (i % total == index) {
                JudgeServer judgeServer = judgeServerList.get(i);
                UpdateWrapper<JudgeServer> judgeServerUpdateWrapper = new UpdateWrapper<>();
                judgeServerUpdateWrapper.set(fixedTag, false)
                        .eq("id", judgeServer.getId())
                        .eq(fixedTag, true);
                boolean isOk = judgeServerEntityService.update(judgeServerUpdateWrapper);
                if (isOk) {
                    return judgeServer;
                }
            }
        }
        return null;
    }

    /**
     * 筛选远程判题账号
     */
    @Transactional(rollbackFor = Exception.class)
    public RemoteJudgeAccount chooseRemoteAccount(String remoteOJAccountType, String username, Boolean isNeedAccountRejudge) {

        // 过滤出当前远程oj可用的账号列表 悲观锁
        List<RemoteJudgeAccount> remoteJudgeAccountList = remoteJudgeAccountMapper.getAvailableAccount(remoteOJAccountType);

        for (RemoteJudgeAccount remoteJudgeAccount : remoteJudgeAccountList) {
            // POJ已有submitId的重判需要使用原来的账号获取结果
            if (isNeedAccountRejudge) {
                if (remoteJudgeAccount.getUsername().equals(username)) {
                    int count = remoteJudgeAccountMapper.updateAccountStatusById(remoteJudgeAccount.getId());
                    if (count > 0) {
                        return remoteJudgeAccount;
                    }
                }
            } else {
                int count = remoteJudgeAccountMapper.updateAccountStatusById(remoteJudgeAccount.getId());
                if (count > 0) {
                    return remoteJudgeAccount;
                }
            }
        }

        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    public HashMap<String, Object> chooseFixedAccount(String remoteOJAccountType) {
        List<Instance> instances = getInstances(JudgeServiceName);
        // 过滤出当前远程可用的账号列表 悲观锁
        QueryWrapper<RemoteJudgeAccount> remoteJudgeAccountQueryWrapper = new QueryWrapper<>();
        remoteJudgeAccountQueryWrapper.eq("oj", remoteOJAccountType)
                .last("for update");

        List<RemoteJudgeAccount> remoteJudgeAccountList = remoteJudgeAccountMapper.selectList(remoteJudgeAccountQueryWrapper);
        int len = remoteJudgeAccountList.size();
        for (int i = 0; i < len && i < instances.size(); i++) {
            RemoteJudgeAccount remoteJudgeAccount = remoteJudgeAccountList.get(i);
            int count = remoteJudgeAccountMapper.updateAccountStatusById(remoteJudgeAccount.getId());
            if (count > 0) {
                HashMap<String, Object> result = new HashMap<>();
                result.put("index", i);
                result.put("size", len);
                result.put("account", remoteJudgeAccount);
                return result;
            }
        }
        return null;
    }

    /**
     * 获取微服务健康实例
     *
     * @param judgeServiceName 注册名
     */
    private List<Instance> getInstances(String judgeServiceName) {
        NamingService namingService = discoveryProperties.namingServiceInstance();

        try {
            return namingService.selectInstances(judgeServiceName, true);
        } catch (NacosException e) {
            log.error("获取微服务健康实例发生异常：", e);
            return Collections.emptyList();
        }
    }
}
